// Provides convenience methods for confirming system.profile content.

// Given a command, build its expected shape in the system profiler.
export function buildCommandProfile(command, sharded) {
    let commandProfile = {};

    if (command.mapReduce) {
        // MapReduce is rewritten to an aggregate pipeline.
        commandProfile["command.aggregate"] = command.mapReduce;
    } else if (command.update) {
        // Updates are batched, but only allow using buildCommandProfile() for an update batch that
        // contains a single update, since the profiler generates separate entries for each update
        // in the batch.
        assert(command.updates.length == 1);
        for (let key in command.updates[0]) {
            commandProfile["command." + key] = command.updates[0][key];
        }
        // Though 'upsert' and 'multi' are optional fields, they are written with the default value
        // in the profiler.
        commandProfile["command.upsert"] = commandProfile["command.upsert"] || false;
        commandProfile["command.multi"] = commandProfile["command.multi"] || false;
    } else if (command.delete) {
        // Deletes are batched, but only allow using buildCommandProfile() for a delete batch that
        // contains a single delete, since the profiler generates separate entries for each delete
        // in the batch.
        assert(command.deletes.length == 1);
        for (let key in command.deletes[0]) {
            commandProfile["command." + key] = command.deletes[0][key];
        }
    } else {
        for (let key in command) {
            commandProfile["command." + key] = command[key];
        }
    }
    return commandProfile;
}

// Retrieve N latest system.profile entries.
export function getNLatestProfilerEntries(profileDB, count, filter) {
    if (filter === null) {
        filter = {};
    }
    var cursor = profileDB.system.profile.find(filter).sort({$natural: -1}).limit(count);
    assert(
        cursor.hasNext(),
        "could not find any entries in the profile collection matching filter: " + tojson(filter));
    return cursor.toArray();
}

// Retrieve latest system.profile entry.
export function getLatestProfilerEntry(profileDB, filter) {
    return getNLatestProfilerEntries(profileDB, 1, filter)[0];
}

/**
 * Throws an assertion if the profiler contains more than 'maxExpectedMatches' entries matching
 * "filter", or if there are no matches. Optional arguments "errorMsgFilter" and "errorMsgProj"
 * limit profiler output if this asserts.
 */
export function profilerHasAtLeastOneAtMostNumMatchingEntriesOrThrow(
    {profileDB, filter, maxExpectedMatches, errorMsgFilter, errorMsgProj}) {
    assert(typeof maxExpectedMatches === 'number' && maxExpectedMatches > 0,
           "'maxExpectedMatches' must be a number > 0");

    const numMatches = profileDB.system.profile.find(filter).itcount();

    assert.gt(numMatches,
              0,
              "Expected at least 1 op matching: " + tojson(filter) + " in profiler " +
                  tojson(profileDB.system.profile.find(errorMsgFilter, errorMsgProj).toArray()));

    assert.lte(numMatches,
               maxExpectedMatches,
               "Expected at most " + maxExpectedMatches + " op(s) matching: " + tojson(filter) +
                   " in profiler " +
                   tojson(profileDB.system.profile.find(errorMsgFilter, errorMsgProj).toArray()));
}

/**
 * Throws an assertion if the profiler does not contain exactly 'numExpectedMatches' entries
 * matching "filter". Optional arguments "errorMsgFilter" and "errorMsgProj" limit profiler output
 * if this asserts.
 */
export function profilerHasNumMatchingEntriesOrThrow(
    {profileDB, filter, numExpectedMatches, errorMsgFilter, errorMsgProj}) {
    assert(typeof numExpectedMatches === 'number' && numExpectedMatches >= 0,
           "'numExpectedMatches' must be a number >= 0");

    assert.eq(profileDB.system.profile.find(filter).itcount(),
              numExpectedMatches,
              "Expected exactly " + numExpectedMatches + " op(s) matching: " + tojson(filter) +
                  " in profiler " +
                  tojson(profileDB.system.profile.find(errorMsgFilter, errorMsgProj).toArray()));
}

/**
 * Throws an assertion if the profiler does not contain any entries matching "filter". Optional
 * arguments "errorMsgFilter" and "errorMsgProj" limit profiler output if this asserts.
 */
export function profilerHasAtLeastOneMatchingEntryOrThrow(
    {profileDB, filter, errorMsgFilter, errorMsgProj}) {
    assert.gte(profileDB.system.profile.find(filter).itcount(),
               1,
               "Expected at least 1 op matching: " + tojson(filter) + " in profiler " +
                   tojson(profileDB.system.profile.find(errorMsgFilter, errorMsgProj).toArray()));
}

/**
 * Throws an assertion if the profiler does not contain exactly one entry matching "filter".
 * Optional arguments "errorMsgFilter" and "errorMsgProj" limit profiler output if this asserts.
 */
export function profilerHasSingleMatchingEntryOrThrow(
    {profileDB, filter, errorMsgFilter, errorMsgProj}) {
    profilerHasNumMatchingEntriesOrThrow({
        profileDB: profileDB,
        filter: filter,
        numExpectedMatches: 1,
        errorMsgFilter: errorMsgFilter,
        errorMsgProj: errorMsgProj
    });
}

/**
 * Throws an assertion if the profiler contains any entries matching "filter". Optional arguments
 * "errorMsgFilter" and "errorMsgProj" limit profiler output if this asserts.
 */
export function profilerHasZeroMatchingEntriesOrThrow(
    {profileDB, filter, errorMsgFilter, errorMsgProj}) {
    profilerHasNumMatchingEntriesOrThrow({
        profileDB: profileDB,
        filter: filter,
        numExpectedMatches: 0,
        errorMsgFilter: errorMsgFilter,
        errorMsgProj: errorMsgProj
    });
}
